import React, { useEffect, useRef, useState } from "react";
import { Button, message, Upload } from "antd";
import { UploadOutlined } from "@ant-design/icons";
import type { GetProp, UploadFile, UploadProps } from "antd";
import OSS from "ali-oss";

const MAX_FILE_SIZE = 1024 * 1024 * 1024 * 2;
type FileType = Parameters<GetProp<UploadProps, "beforeUpload">>[0];

interface STSToken {
  accessKeyId: string;
  accessKeySecret: string;
  securityToken: string;
  expiration: string;
}

interface OSSUploaderProps extends UploadProps {
  OSSRegion: string;
  OSSBucket: string;
  getSTSToken: () => Promise<STSToken>;
}

const OSSUploader: React.FC<OSSUploaderProps> = (props) => {
  const { OSSRegion, OSSBucket, getSTSToken } = props;
  const [fileList, setFileList] = useState<UploadFile[]>([]);
  const [filenameWorker, setFilenameWorker] = useState<Worker | null>(null);
  const stsToken = useRef<STSToken>(null);
  const tokenExpireTime = useRef<number>(0);
  useEffect(() => {
    const filenameWorker = new Worker("/filenameWorker.js");
    setFilenameWorker(filenameWorker);
  }, []);
  // 刷新token
  const refreshSTSToken = async () => {
    const now = Date.now();
    if (!stsToken.current || tokenExpireTime.current - now < 5 * 60 * 1000) {
      try {
        const newToken = await getSTSToken();
        stsToken.current = newToken;
        // 计算过期时间
        const expireTime =
          new Date(newToken.expiration).getTime() - 5 * 60 * 1000;
        tokenExpireTime.current = expireTime;
        return true;
      } catch (error) {
        message.error("获取上传凭证失败");
        return false;
      }
    }
    return true;
  };
  // 初始化OSS客户端
  const initOSSClient = () => {
    if (!stsToken.current) return null;
    return new OSS({
      region: OSSRegion,
      bucket: OSSBucket,
      accessKeyId: stsToken.current.accessKeyId,
      accessKeySecret: stsToken.current.accessKeySecret,
      stsToken: stsToken.current.securityToken,
      refreshSTSToken: async () => {
        const newToken = await getSTSToken();
        stsToken.current = newToken;
        const expireTime =
          new Date(newToken.expiration).getTime() - 5 * 60 * 1000;
        tokenExpireTime.current = expireTime;
        return {
          accessKeyId: newToken.accessKeyId,
          accessKeySecret: newToken.accessKeySecret,
          stsToken: newToken.securityToken,
        };
      },
      refreshSTSTokenInterval: 30 * 60 * 1000,
    });
  };
  // 上传文件
  const uploadFile = async (file: UploadFile) => {
    const tokenRefreshed = await refreshSTSToken();
    if (!tokenRefreshed) return;
    const OSSClient = initOSSClient();
    if (!OSSClient) return;
    if (!file.fileName) return;
    try {
      await OSSClient.get(file.fileName);
      message.success("文件已存在，秒传成功");
      return;
    } catch (error) {
      if ((error as any).code === "NoSuchKey") {
        try {
          await OSSClient.multipartUpload(file.fileName, file.originFileObj, {
            headers: {
              "Content-Type": "application/octet-stream",
            },
          });
          message.success("文件上传成功");
          return;
        } catch (error) {
          message.error("文件上传出错");
          return;
        }
      }
    }
  };
  // 处理上传文件
  const handleBeforeUpload = async (file: FileType) => {
    // 检查文件大小
    if (file.size > MAX_FILE_SIZE) {
      message.error("文件大小不能超过2G");
      return false;
    }
    // 检查文件类型
    if (!(file.type.startsWith("image/") || file.type.startsWith("video/"))) {
      message.error("文件类型必须是图片或者视频");
      return false;
    }
    // 根据文件内容生成hash
    if (!filenameWorker) return false;
    filenameWorker.postMessage(file);
    filenameWorker.onmessage = async (event) => {
      const newFile: UploadFile = {
        uid: file.uid,
        name: file.name,
        fileName: event.data,
        originFileObj: file,
      };
      setFileList([...fileList, newFile]);
      await uploadFile(newFile);
    };
    // 阻止默认上传行为
    return false;
  };
  return (
    <Upload beforeUpload={handleBeforeUpload} fileList={fileList}>
      <Button icon={<UploadOutlined />}>Upload</Button>
    </Upload>
  );
};

export default OSSUploader;
